﻿using System;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace Working.Domínio
{
  [Serializable]
  public class Entidade: MarshalByRefObject, ISerializable, INotifyPropertyChanged,
      INotifyPropertyChanging, IDataErrorInfo
  {
    #region Eventos --------------------------------------------------------------

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
      if(this.PropertyChanged != null)
      {
        PropertyChangedEventArgs args = new PropertyChangedEventArgs(propertyName);
        PropertyChanged(this, args);
      }
    }

    public event PropertyChangingEventHandler PropertyChanging;

    protected void OnPropertyChanging(string propertyName)
    {
      if(this.PropertyChanging != null)
      {
        PropertyChangingEventArgs args = new PropertyChangingEventArgs(propertyName);
        PropertyChanging(this, args);
      }
    }

    #endregion

    #region Contrutores ----------------------------------------------------------

    public Entidade()
    {
      this.validationError = new Dictionary<string, string>();
    }

    public Entidade(int oid)
      : this()
    {
      this.OID = oid;
    }

    public Entidade(SerializationInfo info, StreamingContext context)
      : this()
    {
      this.oid = info.GetInt32("OID");
    }

    #endregion

    #region Propriedades ---------------------------------------------------------

    private Dictionary<string, string> validationError;

    public int OID
    {
      get { return oid; }
      set
      {
        if(value < 0)
          ThrowValidationException("Identificador do objeto inválido.", "OID");

        SetProperty("OID", ref this.oid, value);
      }
    }
    private int oid;

    protected bool IsLoading
    {
      get { return isLoading; }
      set { isLoading = value; }
    }
    private bool isLoading = false;

    public string Error
    {
      get
      {
        if(validationError.Count > 0)
          return string.Join(Environment.NewLine, validationError.Values.ToArray());
        else
          return null;
      }
    }

    public string this[string propertyName]
    {
      get
      {
        if(validationError.ContainsKey(propertyName))
          return validationError[propertyName];
        else
          return null;
      }
    }

    #endregion

    #region Métodos --------------------------------------------------------------

    public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    {
      info.AddValue("OID", this.OID);
    }

    protected void SetProperty<T>(string propertyName, ref T currentValue, T newValue)
    {
      if(!string.IsNullOrEmpty(propertyName))
      {
        if(!this.IsLoading)
        {
          try
          {
            OnPropertyChanging(propertyName);

            currentValue = newValue;
            validationError.Remove(propertyName);

            OnPropertyChanged(propertyName);
          }
          catch(Exception exception)
          {
            SetInternalError(propertyName, exception.Message);
            throw exception;
          }
        }
      }
    }

    protected virtual void BeginLoad()
    {
      this.IsLoading = true;
    }

    protected virtual void EndLoad()
    {
      this.IsLoading = false;
    }

    protected void ThrowValidationException(string message)
    {
      ThrowValidationException(message, string.Empty, null);
    }

    protected void ThrowValidationException(string message, string propertyName, Exception innerException)
    {
      ValidationException exception = new ValidationException(message, innerException);
      ThrowValidationException(exception, propertyName);
    }

    protected void ThrowValidationException(string message, string propertyName)
    {
      ThrowValidationException(message, propertyName, null);
    }

    protected void ThrowValidationException(ValidationException exception)
    {
      ThrowValidationException(exception, null);
    }

    protected void ThrowValidationException(ValidationException exception, string propertyName)
    {
      if(exception != null)
      {
        OnPropertyChanging(propertyName);
        if(propertyName != null && !exception.PropertyName.Equals(propertyName))
          exception.PropertyName = propertyName;

        exception.Source = this.GetType().Name;
        SetInternalError(propertyName, exception.Message);
        throw exception;
      }
    }

    private void SetInternalError(string propertyName, string errorMessage)
    {
      if(!string.IsNullOrEmpty(propertyName) && !string.IsNullOrEmpty(errorMessage))
      {
        OnPropertyChanging("Error");
        validationError[propertyName] = errorMessage;
        OnPropertyChanged("Error");
      }
    }

    #endregion
  }
}
